

##############################################################################
#
#        FUNCTION CREATION
#
##############################################################################
def postfix(expr):
    """Represents expression in postfix operator order.
    
    Description:
        This function parses expr using the postfix operator order 
        "x + y" ==>  "x y +". The output is a list of regular MapleObjects 
        representing the operandes and tuples (MapleObject, n_args) 
        represents operators.
        
    Input:
        expr -- a valid MapleExpr object
        
    Examples:
        >>> aspostfix(x + y) 
        [ x, y, (+, 2)]
        >>> aspostfix(maple.cos(x) * y) 
        [ x, (cos, 1), y, (*, 2) ]
    """
    
    # creates the stack
    nops = maple.nops(expr)
    ops = [ maple.op(i + 1, expr) for i in xrange(nops) ]
    stack = []

    # fill the stack with operands
    for op in ops:
        if isinstance(op, MapleSingle):
            stack.append(op)
        else:
            stack.extend(self.get_AST(op))
            
    # operators are stored as a (operator, nops) tuple
    stack.append((maple.op(0, expr), nops))
    return stack

def ast(expr):
    """Creates the abstract syntatic tree that represents 'expr'.
    
    Description:
        This function parses expr and outputs it in a form similar to LISP
        syntax. For each function application, it returns a tuple: 
        
            func(x, y) <==> (x, y, func) 
        
        which can be composed in more complicated patterns.
        
    Input:
        expr -- a valid MapleExpr object
        
    Examples:
        >>> asAST(x + y) 
        (x, y, +)
        >>> asAST(maple.cos(x) * y**2) 
        ((x, cos), (y, 2, ^), *)
    """
    # creates the stack
    if isinstance(expr, MapleSingle):
        return expr
    else:       
        nops = maple.nops(expr)
        res = list( make_AST(maple.op(i + 1, expr)) for i in xrange(nops) )
        res.append(maple.op(0, expr))
        return tuple(res)

def pyfunction(expr, *vars, **namespace):
    """Returns a Python function which implements expr. Translation between 
    MapleObjects and Python ones are stored in a global dictionary and may be
    overidden by keyword arguments.
    
    Input:
        expr -- a valid MapleObject expression
        vars -- input variables for function. The 1st positional argument can be
                a list of variables. Use this to pack different arguments together 
                in a single array.
        kwds -- a translation rule, substitute all ocurences of variable "key"
                with "value".

    Example:
        >>> mycos = lambda x: 1. - x**2/2.
        >>> expr = maple.cos(x) + y * z
        >>> func = pyfunction(expr, [x, y], z, 'cos'=mycos)
        >>> func([1, 2], 3)
        6.5
    """
    names = {}
    stack = []
    
    expr.__pycommand__(names, stack)
    names.update(namespace)
    namespace  = names.copy()
    
    # generate first version of function to get function code
    stack.insert(0, 'def maple2py_func(%s):' % ', '.join( str(x) for x in vars ))
    cmd = '\n    '.join(stack)
    
    # execute code do define the python function in the given namespace
    exec cmd in namespace
    maple2py_func = namespace['maple2py_func']
    
    # creates docstring  with the autogenerated code --- TODO: format arguments
    cmd_doc = '\n    '.join( ' ' * 8 + x for x in stack)
    doc = 'Autogenerated code from expression: %s\n\n    Function code:\n\n%s\n\n    Inputs:\n\n        %s' % \
        (expr, cmd_doc, ', '.join( str(x) for x in vars ))
    maple2py_func.func_doc = doc
    
    # return it
    return maple2py_func

def pygrad(expr, *vars, **namespace):
    """Returns the gradient of the function. The vec function takes the gradient in the
    variables inside vec and the non-vec returns the gradient over all arguments"""
    difflist = []
    doclist = []
    st_vars = ', '.join( str(x) for x in vars )
    
    # creates a line of code for each derivative
    for var in vars:
        dname = 'dfunc_d%s' % var
        func = pyfunction(maple.diff(expr, var), *vars, **namespace)
        
        # put on namespace and function command
        namespace[dname] = func
        difflist.append('%s(%s)' % (dname, st_vars))
        
        # creates docstring
        doc = func.__doc__.replace('Autogenerated code from expression', 
                                    '    Function %s(%s)' % (dname, st_vars))
        
        # remove the Inputs section
        doc_head, doc_body = doc.split('    Function code:\n\n')
        doc_body = doc_body.split('\n\n    Inputs:')[0]
        
        # remove 1-level identation
        #doc_body = '\n'.join([ x[4:] for x in doc_body.split('\n') ])
        
        # end doc
        doclist.append(doc_head + doc_body)
    
    # creates command
    st_diffs = ', '.join(difflist)
    cmd = 'def maple2py_func(%s):\n    return [%s]' % (st_vars, st_diffs)
        
    # execute code do define the python function in the given namespace
    exec cmd in namespace
    maple2py_func = namespace['maple2py_func']
    
    # creates doc_string
    cmd_doc = '\n'.join([ ' ' * 4 + x for x in cmd.split('\n') ])
   
    # tokens
    lines =  [ 'Autogenerated gradient function for: %s' % expr, 
               '\n\nFunction code:\n\n', cmd_doc,
               '\n\nCode for function derivatives:\n\n', '\n\n'.join(doclist),
               '\n\nInputs:\n\n    ', st_vars ]
    
    # join lines and append to function
    doc = ''.join(lines)
    maple2py_func.func_doc = doc
    
    return maple2py_func
